Editor.Parser = (function() {
  function wordRegexp(words) {
	return new RegExp("^(?:" + words.join("|") + ")$", "i");
  }
  var ops = wordRegexp(["str", "lang", "langmatches", "datatype", "bound", "sameterm", "isiri", "isuri",
						"isblank", "isliteral", "union", "a"]);
  var keywords = wordRegexp(["base", "prefix", "select", "distinct", "reduced", "construct", "describe",
							 "ask", "from", "named", "where", "order", "limit", "offset", "filter", "optional",
							 "graph", "by", "asc", "desc", ]);
  var operatorChars = /[*+\-<>=&|]/;

  var tokenizeSparql = (function() {
	function normal(source, setState) {
	  var ch = source.next();
	  if (ch == "$" || ch == "?") {
		source.nextWhile(matcher(/[\w\d]/));
		return "sp-var";
	  }
	  else if (ch == "<" && !source.applies(matcher(/[\s\u00a0=]/))) {
		source.nextWhile(matcher(/[^\s\u00a0>]/));
		if (source.equals(">")) source.next();
		return "sp-uri";
	  }
	  else if (ch == "\"" || ch == "'") {
		setState(inLiteral(ch));
		return null;
	  }
	  else if (/[{}\(\),\.;\[\]]/.test(ch)) {
		return "sp-punc";
	  }
	  else if (ch == "#") {
		while (!source.endOfLine()) source.next();
		return "sp-comment";
	  }
	  else if (operatorChars.test(ch)) {
		source.nextWhile(matcher(operatorChars));
		return "sp-operator";
	  }
	  else if (ch == ":") {
		source.nextWhile(matcher(/[\w\d\._\-]/));
		return "sp-prefixed";
	  }
	  else {
		source.nextWhile(matcher(/[_\w\d]/));
		if (source.equals(":")) {
		  source.next();
		  source.nextWhile(matcher(/[\w\d_\-]/));
		  return "sp-prefixed";
		}
		var word = source.get(), type;
		if (ops.test(word))
		  type = "sp-operator";
		else if (keywords.test(word))
		  type = "sp-keyword";
		else
		  type = "sp-word";
		return {style: type, content: word};
	  }
	}

	function inLiteral(quote) {
	  return function(source, setState) {
		var escaped = false;
		while (!source.endOfLine()) {
		  var ch = source.next();
		  if (ch == quote && !escaped) {
			setState(normal);
			break;
		  }
		  escaped = !escaped && ch == "\\";
		}
		return "sp-literal";
	  };
	}

	return function(source, startState) {
	  return tokenizer(source, startState || normal);
	};
  })();

  function indentSparql(context) {
	return function(nextChars) {
	  var firstChar = nextChars && nextChars.charAt(0);
	  if (/[\]\}]/.test(firstChar))
		while (context && context.type == "pattern") context = context.prev;

	  var closing = context && firstChar == matching[context.type];
	  if (!context)
		return 0;
	  else if (context.type == "pattern")
		return context.col;
	  else if (context.align)
		return context.col - (closing ? context.width : 0);
	  else
		return context.indent + (closing ? 0 : indentUnit);
	}
  }

  function parseSparql(source) {
	var tokens = tokenizeSparql(source);
	var context = null, indent = 0, col = 0;
	function pushContext(type, width) {
	  context = {prev: context, indent: indent, col: col, type: type, width: width};
	}
	function popContext() {
	  context = context.prev;
	}

	var iter = {
	  next: function() {
		var token = tokens.next(), type = token.style, content = token.content, width = token.value.length;

		if (content == "\n") {
		  token.indentation = indentSparql(context);
		  indent = col = 0;
		  if (context && context.align == null) context.align = false;
		}
		else if (type == "whitespace" && col == 0) {
		  indent = width;
		}
		else if (type != "sp-comment" && context && context.align == null) {
		  context.align = true;
		}

		if (content != "\n") col += width;

		if (/[\[\{\(]/.test(content)) {
		  pushContext(content, width);
		}
		else if (/[\]\}\)]/.test(content)) {
		  while (context && context.type == "pattern")
			popContext();
		  if (context && content == matching[context.type])
			popContext();
		}
		else if (content == "." && context && context.type == "pattern") {
		  popContext();
		}
		else if ((type == "sp-word" || type == "sp-prefixed" || type == "sp-uri" || type == "sp-var" || type == "sp-literal") &&
				 context && /[\{\[]/.test(context.type)) {
		  pushContext("pattern", width);
		}

		return token;
	  },

	  copy: function() {
		var _context = context, _indent = indent, _col = col, _tokenState = tokens.state;
		return function(source) {
		  tokens = tokenizeSparql(source, _tokenState);
		  context = _context;
		  indent = _indent;
		  col = _col;
		  return iter;
		};
	  }
	};
	return iter;
  }

  return {make: parseSparql, electricChars: "}]"};
})();
